package net.csdn.business.auth.config;

import com.alibaba.fastjson2.JSON;
import feign.Target;
import lombok.extern.slf4j.Slf4j;
import net.csdn.business.common.constant.CommonConstants;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.BeansException;
import org.springframework.cloud.context.environment.EnvironmentChangeEvent;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author likun
 * @date 2022/7/16 20:06
 */
@Slf4j
@Component
@SuppressWarnings("all")
public class NacosConfigChangeListener implements ApplicationListener<EnvironmentChangeEvent>, ApplicationContextAware {
    private ApplicationContext applicationContext;
    Map<String, List<Object>> nameToFeignInstance = new ConcurrentHashMap<>();

    @Override
    public void onApplicationEvent(@NotNull EnvironmentChangeEvent event) {
        reCreateFeignClient(event.getKeys());
    }

    @Override
    public void setApplicationContext(@NotNull ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
        // 扫描程序中的feign client domain配置
        scanFeignClientDomain();
    }

    private void reCreateFeignClient(Set<String> changedKeys) {
        if (CollectionUtils.isEmpty(changedKeys)) {
            return;
        }

        Environment environment = applicationContext.getEnvironment();
        for (String key : changedKeys) {
            String value = environment.getProperty(key);
            if (StringUtils.isEmpty(value) || !StringUtils.endsWithIgnoreCase(key, ".domain")) {
                continue;
            }
            List<Object> beans = nameToFeignInstance.get("${" + key + "}");
            if (CollectionUtils.isEmpty(beans)) {
                continue;
            }
            for (Object proxy : beans) {
                try {
                    Field field = proxy.getClass().getSuperclass().getDeclaredField("h");
                    field.setAccessible(true);
                    Object o = field.get(proxy);
                    Field targetField = o.getClass().getDeclaredField("target");
                    targetField.setAccessible(true);
                    Object o2 = targetField.get(o);
                    if (!(o2 instanceof Target.HardCodedTarget)) {
                        log.info("not HardCodedTarget pass");
                        continue;
                    }
                    Target.HardCodedTarget o1 = (Target.HardCodedTarget) o2;
                    Field url = o1.getClass().getDeclaredField("url");
                    url.setAccessible(true);
                    url.set(o1, value);
                    log.info("feignClient.url set to {}", value);
                } catch (Exception e) {
                    log.error("change FeignClient url error, {}", JSON.toJSONString(e));
                }
            }
        }
    }

    private void scanFeignClientDomain() {
        String[] beanNamesForAnnotation = this.applicationContext.getBeanNamesForAnnotation(FeignClient.class);
        if (ObjectUtils.isEmpty(beanNamesForAnnotation) || beanNamesForAnnotation.length == CommonConstants.INT_ZERO) {
            return;
        }

        for (String name : beanNamesForAnnotation) {
            try {
                Class<?> clazz = Class.forName(name);
                FeignClient annotation = clazz.getAnnotation(FeignClient.class);
                List<Object> beans = Optional.ofNullable(nameToFeignInstance.get(annotation.url())).orElse(new ArrayList<>());
                beans.add(applicationContext.getBean(name));
                nameToFeignInstance.put(annotation.url(), beans);
            } catch (ClassNotFoundException e) {
                log.error("process feignMap error, {}", JSON.toJSONString(e));
            }
        }
    }
}